Optimize mapObj#202
Conversation
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
| const mappedObj = {}; | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| const [newKey, newValue] = fn.call(undefined, [key, obj[key]]); |
There was a problem hiding this comment.
i'd think that fn([key, obj[key]]) would be much faster than using .call?
There was a problem hiding this comment.
I thought so too, but this seemed to be a bit better, at least in Chrome. I was thinking it might have to do with thisObj being undefined, but maybe I'll take another look at the perf here.
There was a problem hiding this comment.
They seem to be about the same. I'll change it to fn() because it is less weird than fn.call.
I thought maybe it would be faster by removing the array here and passing args directly, but that didn't seem to really make a difference.
| const keys = Object.keys(obj); | ||
| const mappedObj = {}; | ||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; |
There was a problem hiding this comment.
if you're looking for performance, avoiding block-scoped vars in loops is a good way to do it - ie, repeating the keys[i] in two places probably will give you a slight bump
There was a problem hiding this comment.
Yeah, maybe I'll give it a go. I originally avoided it for readability, and I think the performance improvement will be nominal. But maybe it will help with GC.
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
|
JSPerf: https://jsperf.com/aphrodite-mapobj-refactor/1 I'm seeing the new version about 10% faster in Chrome, 13% faster in Firefox. |
xymostech
left a comment
There was a problem hiding this comment.
LGTM! Some small nit. Thanks for doing such thorough performance testing!
| const [newKey, newValue] = fn([keys[i], obj[keys[i]]]); | ||
| mappedObj[newKey] = newValue; | ||
| } | ||
| return mappedObj; |
There was a problem hiding this comment.
nit: we use 4 space indents
I did some profiling of StyleSheet.create and noticed that mapObj was a
good target for optimization.
Methodology
I created an HTML document with the following code in it:
```html
<script type="text/javascript" src="./dist/aphrodite.umd.js"></script>
<!-- setup -->
<script type="text/javascript">
// build up an array of styles objects to run our test on
var styles = [];
for (var i = 0; i < 10000; i += 1) {
styles.push({
[`a${Math.random()}`]: {
[`a${Math.random()}`]: Math.random(),
[`b${Math.random()}`]: String(Math.random()),
[`c${Math.random()}`]: String(Math.random()),
},
[`b${Math.random()}`]: {
[`a${Math.random()}`]: Math.random(),
[`b${Math.random()}`]: String(Math.random()),
[`c${Math.random()}`]: String(Math.random()),
},
[`c${Math.random()}`]: {
[`a${Math.random()}`]: Math.random(),
[`b${Math.random()}`]: String(Math.random()),
[`c${Math.random()}`]: String(Math.random()),
},
});
}
</script>
<!-- test -->
<script type="text/javascript">
setTimeout(() => {
performance.mark('start_run');
for (var i = 0; i < styles.length; i += 1) {
// prevent caching optimizations
eval('');
performance.mark('start_stylesheet_create');
aphrodite.StyleSheet.create(styles[i]);
performance.mark('end_stylesheet_create');
performance.measure(
'aphrodite.StyleSheet.create',
'start_stylesheet_create',
'end_stylesheet_create'
);
performance.clearMarks('start_stylesheet_create', 'end_stylesheet_create');
}
performance.mark('end_run');
performance.measure(`Benchmark ${styles.length}`, 'start_run', 'end_run');
performance.clearMarks();
});
</script>
```
Then, looking at the timeline tool in Chrome, I loaded the page a few
times before and after this change. Similarly, I ran a CPU profile
before and after this change through 5 page reloads each.
In this test, the timeline was not very helpful, I think because of the
testing overhead. However, the CPU profile was very clear. Before this
change, normalizing for the callback showing up in a different part of
the profile, `mapObj` took ~317ms and after this change, it drops to
~211ms. `StyleSheet.create` drops from ~755ms to ~670ms or roughly 11%
faster. The rest of the time in `StyleSheet.create` is spent in
`murmurhash2_32_gc` and `hashObject`.
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
* Replace murmur hash with djb2 hash In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to #202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms). * Depend on string-hash for djb2 hashing algorithm This is where I copied the code for this algorithm from, seems like we might as well just bring in the dependency for it.
I did some profiling of StyleSheet.create and noticed that mapObj was a
good target for optimization.
Methodology
I created an HTML document with the following code in it:
Then, looking at the timeline tool in Chrome, I loaded the page a few
times before and after this change. Similarly, I ran a CPU profile
before and after this change through 5 page reloads each.
In this test, the timeline was not very helpful, I think because of the
testing overhead. However, the CPU profile was very clear. Before this
change, normalizing for the callback showing up in a different part of
the profile,
mapObjtook ~317ms and after this change, it drops to~211ms.
StyleSheet.createdrops from ~755ms to ~670ms or roughly 11%faster. The rest of the time in
StyleSheet.createis spent inmurmurhash2_32_gcandhashObject.cc @ljharb